Accessibility Violation Reporting
**Purpose:** Standardized format for reporting WCAG violations with remediation guidance
**Version:** 1.0
**Last Updated:** 2026-03-22
---
Overview
This document defines the standardized format for reporting accessibility violations found during automated testing (Tier 1 & Tier 2) and manual testing (Tier 3). Each violation includes WCAG success criteria, severity level, affected component/page, issue description, remediation steps, and code examples.
**Why Standardized Reporting Matters:**
- Consistent violation tracking across teams
- Clear remediation guidance for developers
- WCAG criteria reference for compliance auditing
- Severity-based prioritization for fixing
- Progress tracking over time
---
Violation Format
Each violation report MUST include:
Required Fields
- **WCAG Success Criteria**
- Format: "WCAG 2.1 AA [X.X.X] - [Criteria Name]"
- Example: "WCAG 2.1 AA 1.1.1 - Non-text Content"
- **Severity Level**
- Values:
critical,serious,moderate - See severity definitions below
- **Affected Component/Page**
- Component name or page path
- Example: "Login form (/login)" or "Button component"
- **Issue Description**
- Clear explanation of the accessibility violation
- Why it's a problem for users
- When it occurs
- **Remediation Steps**
- Specific code changes required
- Before/after code examples
- Testing steps to verify fix
- **Verification Checklist**
- Automated test passes
- Screen reader announces correctly
- Keyboard navigation works
Optional Fields
- **Impact**
- User impact description
- Which assistive technologies affected
- Frequency of occurrence
- **Screenshots**
- Visual evidence (if applicable)
- Screen reader output capture
---
Severity Levels
Critical
**Definition:** Blocks task completion, prevents user from using core functionality
**Examples:**
- Unlabeled form submit button (cannot submit form)
- Missing form labels (cannot input data)
- No submit button at all (cannot complete action)
- Keyboard trap (cannot exit component)
- Missing alt text on informative image (cannot understand content)
**Remediation Timeline:** Fix immediately, block deployment
**User Impact:** Users cannot complete critical tasks
---
Serious
**Definition:** Major hindrance, significantly degrades experience but allows task completion with difficulty
**Examples:**
- Missing alt text on decorative image (confusing but not blocking)
- Low contrast text (difficult to read but possible)
- Missing skip navigation link (tedious navigation)
- Unclear link text ("click here")
- Heading hierarchy broken (difficult navigation)
**Remediation Timeline:** Fix within sprint, prioritize above moderate
**User Impact:** Users can complete tasks but with significant difficulty
---
Moderate
**Definition:** Minor hindrance, slightly degrades experience but doesn't prevent task completion
**Examples:**
- Skip link not visible on focus (hidden until focused)
- Missing lang attribute (screen reader uses wrong language)
- Redundant links (same link text to different destinations)
- Missing aria-current on active navigation
- Empty links or buttons
**Remediation Timeline:** Fix within quarter, backlog if needed
**User Impact:** Minor inconvenience, doesn't significantly hinder task completion
---
Reporting Template
Use this template when documenting accessibility violations:
## [Component/Page] - [WCAG Criteria]
**Severity:** Critical/Serious/Moderate
**WCAG:** 2.1 AA [X.X.X] - [Criteria Name]
**Impact:** [User impact description]
**Discovered:** [Automated test / Manual test - Date]
### Issue
[Clear description of the accessibility violation. Why is this a problem? Which users are affected? How does it prevent/hinder task completion?]
### How to Test
1. [Step 1: Navigate to...]
2. [Step 2: Activate screen reader...]
3. [Step 3: Observe that...]
**Expected:** [What should happen]
**Actual:** [What actually happens]
### Remediation
#### Before (Incorrect)// [Code showing the accessibility violation]
<Button>×</Button>
#### After (Correct)// [Fixed code with accessibility improvements]
<button aria-label="Close dialog" onClick={onClose}>
×
</button>
#### Changes Made
- [ ] Added aria-label to icon-only button
- [ ] Ensured keyboard navigation works
- [ ] Verified screen reader announcement
### Verification
- [ ] Automated test passes (jest-axe / axe-core)
- [ ] Screen reader announces correctly (NVDA + VoiceOver)
- [ ] Keyboard navigation works (Tab, Enter, Escape)
- [ ] Visual focus indicator visible
### Related Resources
- WCAG Criteria: [Link to criteria explanation]
- axe Rule: [Link to axe-core rule documentation]
- Similar Issue: [Link to related issue if applicable]---
Priority Order for Remediation
**When multiple violations exist, fix in this order:**
1. Critical (Immediate)
- Blocks task completion
- Prevents users from using core functionality
- Must fix before deployment
2. Serious (Within Sprint)
- Major barriers to task completion
- Significantly degrades experience
- Users can complete tasks but with difficulty
3. Moderate (Within Quarter)
- Minor hindrances
- Slight degradation of experience
- Doesn't prevent task completion
**Work Prioritization Formula:**
Priority = (Severity Weight) × (User Impact) × (Frequency)
Severity Weights:
- Critical: 10
- Serious: 5
- Moderate: 2
User Impact:
- All users: 3
- Most users: 2
- Some users: 1
Frequency:
- Always: 3
- Often: 2
- Rarely: 1**Example Calculation:**
- Critical (10) × All users (3) × Always (3) = 90 (HIGHEST PRIORITY)
- Moderate (2) × Some users (1) × Rarely (1) = 2 (LOWEST PRIORITY)
---
Tracking Violations
GitHub Issues
Track accessibility violations in GitHub issues with labels:
**Required Labels:**
a11y- All accessibility issuesa11y-critical- Critical violations (blocks deployment)a11y-serious- Serious violations (fix in sprint)a11y-moderate- Moderate violations (fix in quarter)a11y-automated- Found by automated testsa11y-manual- Found by manual testing
**Issue Title Format:**
[A11y] [Component/Page]: [WCAG Criteria] - [Brief Description]
Examples:
[A11y] Login form: WCAG 1.3.1 - Missing form labels
[A11y] Button component: WCAG 2.4.7 - Focus not visible
[A11y] Pricing page: WCAG 1.4.3 - Low contrast text**Issue Template:**
## Accessibility Violation
**Severity:** {{severity}}
**WCAG:** {{wcag_criteria}}
**Component/Page:** {{component_or_page}}
**Discovered:** {{date}} via {{automated_test | manual_test}}
### Issue
{{issue_description}}
### User Impact
{{user_impact_description}}
### How to Test
{{testing_steps}}
### Remediation
{{remediation_steps_with_code_examples}}
### Verification
- [ ] Automated test passes
- [ ] Screen reader announces correctly
- [ ] Keyboard navigation works
### Severity Priority
{{priority_justification}}---
Common Violations and Fixes
1. Missing Alt Text (WCAG 1.1.1 - Non-text Content)
**Severity:** Serious (informative images), Critical (functional images)
**Issue:** Images missing alt text attribute
**Example Fix:**
// Before (Incorrect)
<img src="logo.png" />
<img src="warning-icon.png" />
<img src="decorative-pattern.png" />
// After (Correct)
<img src="logo.png" alt="Company Logo" />
<img src="warning-icon.png" alt="Warning: Action required" />
<img src="decorative-pattern.png" alt="" role="presentation" />**Rules:**
- Informative images: Describe content
- Functional images: Describe function (e.g., "Search button")
- Decorative images: Use
alt=""androle="presentation"
**Testing:**
# Automated test
npx jest-axe
# Manual: Screen reader announces alt text or skips decorative images---
2. Missing Form Labels (WCAG 1.3.1 - Info and Relationships)
**Severity:** Critical (blocks form submission)
**Issue:** Form inputs missing associated labels
**Example Fix:**
// Before (Incorrect)
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<input type="checkbox" /> Remember me
// After (Correct)
<label for="email">Email</label>
<input id="email" type="email" aria-required="true" />
<label for="password">Password</label>
<input id="password" type="email" aria-required="true" />
<label>
<input type="checkbox" name="remember" />
Remember me
</label>**Rules:**
- Always use
<label for="id">withid="id"on input - Use
aria-required="true"for required fields - For checkboxes/radios, wrap label around input
**Testing:**
# Automated test
npx jest-axe
# Manual: Screen reader announces label when landing on input---
3. Low Color Contrast (WCAG 1.4.3 - Contrast (Minimum))
**Severity:** Serious (text), Moderate (UI components)
**Issue:** Text contrast fails 4.5:1 ratio for normal text, 3:1 for large text/UI components
**Example Fix:**
/* Before (Incorrect - Fails 4.5:1) */
.text {
color: #999; /* Light gray on white = 2.8:1 ratio */
background: #fff;
}
.button {
color: #fff;
background: #3498db; /* Blue on white = 2.7:1 ratio */
}
/* After (Correct - Passes 4.5:1) */
.text {
color: #595959; /* Dark gray on white = 7.0:1 ratio */
background: #fff;
}
.button {
color: #fff;
background: #0066cc; /* Darker blue on white = 5.7:1 ratio */
}**Rules:**
- Normal text (<18pt): 4.5:1 minimum contrast
- Large text (≥18pt or ≥14pt bold): 3:1 minimum
- UI components (icons, borders): 3:1 minimum
**Testing:**
# Automated test
npm run test:a11y:contrast
# Manual: Use axe DevTools or WebAIM Contrast Checker---
4. Focus Not Visible (WCAG 2.4.7 - Focus Visible)
**Severity:** Serious (keyboard users)
**Issue:** No visible focus indicator on interactive elements
**Example Fix:**
/* Before (Incorrect) */
button:focus {
outline: none; /* Removes focus indicator */
}
/* After (Correct) */
button:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Alternative: Custom focus indicator */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.5);
background-color: #e6f2ff;
}**Rules:**
- Always provide visible focus indicator
- Use
:focusfor mouse + keyboard,:focus-visiblefor keyboard only - Minimum 2px outline, offset from element
- Color must meet 3:1 contrast against background
**Testing:**
# Manual: Tab through page, verify focus visible on all interactive elements---
5. Missing ARIA Labels on Icon-Only Buttons (WCAG 2.5.3 - Label in Name)
**Severity:** Critical (icon-only buttons)
**Issue:** Buttons with only icons have no accessible name
**Example Fix:**
// Before (Incorrect)
<button onClick={onClose}>
<XIcon />
</button>
<button onClick={onSearch}>
<SearchIcon />
</button>
// After (Correct)
<button onClick={onClose} aria-label="Close dialog">
<XIcon />
</button>
<button onClick={onSearch} aria-label="Search">
<SearchIcon />
</button>**Rules:**
- Icon-only buttons MUST have
aria-label - Label should describe button action, not icon name
- Use "Close dialog", not "X icon"
- Use "Search", not "Magnifying glass"
**Testing:**
# Automated test
npx jest-axe
# Manual: Screen reader announces "Close dialog button"---
6. Modal Not Announced (WCAG 4.1.2 - Name, Role, Value)
**Severity:** Serious (modals)
**Issue:** Modal dialogs not announced to screen readers
**Example Fix:**
// Before (Incorrect)
<div className="modal">
<h2>Delete Account</h2>
<p>Are you sure?</p>
<button onClick={onConfirm}>Yes</button>
<button onClick={onCancel}>No</button>
</div>
// After (Correct)
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className="modal"
>
<h2 id="modal-title">Delete Account</h2>
<p>Are you sure?</p>
<button onClick={onConfirm}>Yes, delete</button>
<button onClick={onCancel}>No, cancel</button>
</div>**Rules:**
- Add
role="dialog"to modal container - Add
aria-modal="true"to trap focus - Add
aria-labelledbypointing to modal title - Implement focus trap (JavaScript)
- Return focus to trigger on close
**Testing:**
# Automated test
npm run test:a11y
# Manual: Screen reader announces "dialog appeared"---
7. Broken Heading Hierarchy (WCAG 1.3.1 - Info and Relationships)
**Severity:** Moderate (navigation difficulty)
**Issue:** Heading levels skipped (h1 → h3), no h1 on page
**Example Fix:**
// Before (Incorrect - Skips h2)
<h1>Page Title</h1>
<h3>Section Title</h3>
<h4>Subsection</h4>
// After (Correct - No skipped levels)
<h1>Page Title</h1>
<h2>Section Title</h2>
<h3>Subsection</h3>**Rules:**
- Always start with h1 (one per page)
- Don't skip heading levels (h1 → h2 → h3)
- Don't use headings for styling (use CSS classes)
- Nest headings correctly (h2 under h1, h3 under h2)
**Testing:**
# Automated test
npx jest-axe
# Manual: Screen reader heading navigation works correctly---
8. Form Errors Not Announced (WCAG 3.3.1 - Error Identification)
**Severity:** Critical (form validation)
**Issue:** Form errors not announced to screen readers
**Example Fix:**
// Before (Incorrect)
{errors.email && <span className="error">{errors.email}</span>}
<input type="email" id="email" />
// After (Correct)
{errors.email && (
<span role="alert" aria-live="polite" className="error">
{errors.email}
</span>
)}
<input
type="email"
id="email"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? "email-error" : undefined}
/>
<span id="email-error" className="error">
{errors.email}
</span>**Rules:**
- Add
role="alert"to error messages (announced immediately) - Add
aria-invalid="true"to invalid inputs - Add
aria-describedbypointing to error message - Display errors inline, not only in alert()
**Testing:**
# Automated test
npx jest-axe
# Manual: Screen reader announces error after invalid submission---
9. Missing Skip Navigation Link (WCAG 2.4.1 - Bypass Blocks)
**Severity:** Moderate (navigation efficiency)
**Issue:** No way to skip navigation and jump to main content
**Example Fix:**
// Before (Incorrect - No skip link)
<header>...</header>
<main>...</main>
// After (Correct - Skip link added)
<a
href="#main-content"
className="skip-link"
>
Skip to main content
</a>
<header>...</header>
<main id="main-content">...</main>**CSS:**
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
z-index: 100;
}
.skip-link:focus {
top: 0;
}**Rules:**
- Add skip link as first focusable element on page
- Link target should be
idof<main>or first heading - Make skip link visible on focus
- Text should be descriptive ("Skip to main content")
**Testing:**
# Manual: Tab from top of page, skip link appears and works---
10. Duplicate IDs (WCAG 4.1.1 - Parsing)
**Severity:** Serious (breaks accessibility tree)
**Issue:** Multiple elements with same ID attribute
**Example Fix:**
// Before (Incorrect - Duplicate IDs)
{items.map((item) => (
<div key={item.id}>
<input id="name" name="name" />
<label htmlFor="name">Name</label>
</div>
))}
// After (Correct - Unique IDs)
{items.map((item, index) => (
<div key={item.id}>
<input id={`name-${index}`} name={`name-${index}`} />
<label htmlFor={`name-${index}`}>Name</label>
</div>
))}**Rules:**
- IDs MUST be unique within page
- Use generated IDs for repeated components (useId hook, index)
- Test for duplicates with axe-core
**Testing:**
# Automated test
npm run test:a11y
# Error: "The id 'name' is already used"---
Remediation Workflow
1. Violation Discovered
**Automated Test (CI/CD):**
- GitHub Actions workflow fails
- Report generated and uploaded as artifact
- Issue created automatically (if configured)
**Manual Test (Quarterly Audit):**
- Tester documents violation in report
- Issue created manually by QA team
2. Issue Triage
**Assign Severity:**
- Use severity definitions above
- Consider user impact and frequency
- Add appropriate labels (
a11y-critical,a11y-serious,a11y-moderate)
**Prioritize:**
- Critical: Assign to current sprint, block deployment
- Serious: Assign to current sprint, prioritize above moderate
- Moderate: Add to backlog, schedule within quarter
3. Developer Implements Fix
**Follow remediation steps:**
- Read issue description and WCAG criteria
- Review code examples in issue
- Implement fix using provided template
- Run automated tests locally (
npm run test:a11y) - Test with screen reader (if critical/serious)
4. Verification
**Automated Tests:**
# Run full accessibility suite
npm run test:a11y:full
# Verify specific fix
npm run test:a11y:unit # Component tests
npm run test:a11y # E2E tests**Manual Tests (for critical/serious):**
- [ ] Test with NVDA (Windows + Firefox)
- [ ] Test with VoiceOver (macOS + Safari)
- [ ] Test keyboard navigation (Tab, Enter, Escape)
- [ ] Verify fix addresses reported issue
5. Close Issue
**When issue is resolved:**
- All automated tests passing
- Manual verification complete (if required)
- Fix deployed to production
- Issue closed with reference to commit/PR
---
Progress Tracking
Accessibility Debt Metrics
Track these metrics monthly:
accessibility_debt:
critical: 0 # Target: 0
serious: 5 # Target: <5
moderate: 23 # Target: <25
remediation_rate:
new_violations_this_month: 8
fixed_this_month: 12
trend: "decreasing" # Good!
compliance:
wcag_2_1_aa_compliant: false
estimated_compliance_date: "2026-06-01"Quarterly Reports
Generate quarterly report showing:
- **Violations by Severity:**
- Critical: [count] (target: 0)
- Serious: [count] (target: <5)
- Moderate: [count] (target: <25)
- **Violations by WCAG Criteria:**
- Most common violations (top 10)
- Target for developer training
- **Remediation Progress:**
- Fixed this quarter: [count]
- New this quarter: [count]
- Net change: [+/-]
- **Compliance Status:**
- % WCAG 2.1 AA compliant
- Estimated full compliance date
---
Resources
**WCAG 2.1 AA Quick Reference:**
https://www.w3.org/WAI/WCAG21/quickref/
**axe-core Rule Documentation:**
https://www.deque.com/axe/core-docs/
**WebAIM Accessibility Checklist:**
https://webaim.org/standards/wcag/checklist
**WAI-ARIA Authoring Practices:**
https://www.w3.org/WAI/ARIA/apg/
**Accessibility Testing Tools:**
- axe DevTools: https://www.deque.com/axe/devtools/
- WAVE: https://wave.webaim.org/
- Lighthouse: chrome://lighthouse
---
**Reporting Version:** 1.0
**Last Updated:** 2026-03-22
**Next Review:** 2026-06-22 (Quarterly)
**Owner:** QA Team + Development Team
**Related Documentation:**
docs/accessibility/THREE_TIER_STRATEGY.md(overall strategy)docs/accessibility/SCREEN_READER_TESTING.md(procedures)docs/accessibility/MANUAL_TESTING_CHECKLIST.md(checklist)